package com.firstresponder.location;

import java.util.ArrayList;

import com.firstresponder.util.MessageSender;

import android.location.Location;
import android.util.Log;

/**
 * A class which infers whether a person is getting closer from his/her current
 * location to the current destination.
 * 
 * @author Gergana Stoykova
 * 
 */
public class InferenceManager {

	private static MessageSender MESSAGE_SENDER = MessageSender.getInstance();
	private static final String TAG = "ResponseInference";
	private ArrayList<Float> distances = new ArrayList<Float>();
	private ArrayList<Location> checkpoints = new ArrayList<Location>();
	private final double WITHIN_RADIUS = 300; // radius around destination in
												// meters
	private final double OUTSIDE_OF_RADIUS = 24000;
	private final double MIN_NUMBER_OF_CHECKPOINTS = 3;
	private final double MIN_DIST_BETW_LOC = 100; // min distance between 2
													// checkpoints
	private final int FIRST_INFERENCE = 3;// inferring at THIRD minute
	private final int MIDDLE_INFERENCE = 5; // inferring at FIFTH minute
	private final int LAST_INFERENCE = 7; // inferring at SEVENTH minute
	private int counter = 0; // counter of minutes, incrementing with every
								// onTick

	/**
	 * Infers whether person is getting closer to his/her destination
	 * 
	 * @param currGpsLocation
	 *            - current location gotten from GPS provider
	 * @param currDestination
	 *            - current destination (from database and/or SMS)
	 */
	public void doInference(Location currGpsLocation, Location currDestination) {
		counter = counter + 1;
		Log.d(TAG, "counter is at: " + counter + " minutes");

		if (currGpsLocation != null) {
			Log.d(TAG, "initial GPS available at minute " + counter);
			addCurrentLocations(currGpsLocation);
			Log.d(TAG, "current location added to array list");
			addNewDistance(currGpsLocation, currDestination);
			Log.d(TAG, "current distance added to array list");
			if ((counter == FIRST_INFERENCE) || (counter == MIDDLE_INFERENCE)
					|| (counter == LAST_INFERENCE)) {
				Log.d(TAG, "counter is at minute " + counter);
				compareDistances(currGpsLocation);
				Log.d(TAG, "distances compared");
			}
		}

		else if ((counter == FIRST_INFERENCE) || (counter == MIDDLE_INFERENCE)
				|| (counter == LAST_INFERENCE)) {
			Log.d(TAG, "no gps coordinates at minute " + counter);
		}
	}

	/**
	 * Computes the current distance from current destination to current
	 * location, and adds it to an array of floats
	 * 
	 * @param currGpsLocation
	 *            - current location gotten from GPS provider
	 * @param currDestination
	 *            - current destination (from database and/or SMS)
	 * 
	 */
	private void addNewDistance(Location currGpsLocation,
			Location currDestination) {
		float dist = NearnessCalculator.distanceToDestination(
				currGpsLocation.getLatitude(), currGpsLocation.getLongitude(),
				currDestination.getLatitude(), currDestination.getLongitude());
		distances.add(dist);
		Log.d(TAG, "straight line distance #" + distances.size()
				+ " to destination: " + dist);

	}

	/**
	 * Compares the distance from current location to current destination with
	 * the previous ones and "infers" whether distances are getting smaller
	 * (thus the person is coming closer)
	 */
	private void compareDistances(Location currGpsLocation) {

		Log.d(TAG, "in compareDistances");

		// within small radius
		if (distances.get((distances.size() - 1)) < WITHIN_RADIUS) {
			Log.d(TAG,
					"inferred YES-response when in immediate distance to destination");
			// send "inferred YES-response" SMS message
			MESSAGE_SENDER.sendYesResponseMessage("INF_YES");
		}

		// outside acceptable radius, too far to make it
		else if (distances.get((distances.size() - 1)) > OUTSIDE_OF_RADIUS) {
			Log.d(TAG,
					"inferred NO-response when distance to destination too large");
			// send "inferred NO-response" SMS message
			MESSAGE_SENDER.sendNoResponseMessage("INF_NO");
		}

		else if (distances.size() >= MIN_NUMBER_OF_CHECKPOINTS) {
			Log.d(TAG, "size of distances arraylist " + distances.size());

			// if walking/moving slowly
			if (checkpoints.size() >= 2) {
				if (isMovingSlowly()) {
					Log.d(TAG, "person moving slowly, inferring no");
					// Send "inferred NO-response" SMS message
					MESSAGE_SENDER.sendNoResponseMessage("INF_NO");
				}
			}
			// if last distance less than first one
			else if ((distances.get(0) > distances.get(distances.size() - 1))) {
				Log.d(TAG,
						"inferred YES-response - last distance smaller than first");
				// send "inferred YES-response" SMS message
				MESSAGE_SENDER.sendYesResponseMessage("INF_YES");
			}

			else if ((distances.get(0) < distances.get(distances.size() - 1))) {
				Log.d(TAG,
						"inferred NO-response, last distance to destination greater than first");
				// Send "inferred NO-response" SMS message
				MESSAGE_SENDER.sendNoResponseMessage("INF_NO");
			}
		}

		else {
			Log.d(TAG, "inferred NO-response");
			// Send "inferred NO-response" SMS message
			MESSAGE_SENDER.sendNoResponseMessage("INF_NO");
		}
		Log.d(TAG,
				"end of compareDistances method, size of distances arraylist "
						+ distances.size());

	}

	/**
	 * Checks to see if the person is moving slowly (i.e. walking) via checking
	 * the distance between the checkpoints themselves and comparing them to
	 * what the average distance would have been when a person is "walking"
	 * (which is about 85 meters/minute, or ~100 m)
	 * 
	 * @return TRUE if person is moving slowly (walking); FALSE if person is not
	 *         moving slowly
	 */
	private boolean isMovingSlowly() {
		int slowcounter = 0;
		int maxSlowLimit = 0;
		switch (counter) {
		case FIRST_INFERENCE:
			maxSlowLimit = 2;
			break;
		case MIDDLE_INFERENCE:
			maxSlowLimit = 3;
			break;
		case LAST_INFERENCE:
			maxSlowLimit = 5;
			break;
		}
		Log.d(TAG, "size of checkpoints: " + checkpoints.size());
		Log.d(TAG, "Max limit for slow counter is: " + maxSlowLimit);
		for (int i = 1; i < checkpoints.size(); i++) {
			if ((checkpoints.get(i - 1).distanceTo(checkpoints.get(i))) <= MIN_DIST_BETW_LOC) {
				slowcounter++;
				Log.d(TAG, "slow counter is at: " + slowcounter);
				if (slowcounter >= maxSlowLimit) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Keeps an array list of previous "current locations" aka checkpoints
	 * 
	 * @param currGpsLocation
	 *            - current location gotten from GPS provider
	 * 
	 */
	public void addCurrentLocations(Location currGpsLocation) {
		checkpoints.add(currGpsLocation);
	}
}